介绍
代理(Proxy)模式:为其他对象提供一种代理以控制这个对象的访问。代理模式也叫委托模式,属于结构型模式。
在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买。又如找女朋友、找保姆、找工作等都可以通过找中介完成。
在软件设计中,使用代理模式的例子也很多。例如,要访问的远程对象比较大(如视频或大图像等),其下载要花很多时间。还有因为安全原因需要屏蔽客户端直接访问真实对象,如某单位的内部数据库等。
优点
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
缺点
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度;
使用场景
当无法或不想直接访问某个对象或访问某个对象存在困难时,可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口。
结构与实现
模式包含以下主要角色。
- Subject(抽象主题类):接口或者抽象类,声明真实主题与代理的共同接口方法。
- RealSubject(真实主题类):也叫做被代理类或被委托类,定义了代理所表示的真实对象,负责具体业务逻辑的执行,客户端可以通过代理类间接的调用真实主题类的方法。
- Proxy(代理类):也叫委托类,持有对真实主题类的引用,在其所实现的接口方法中调用真实主题类中相应的接口方法执行。
其结构图如下图所示。
代理模式的实现代码如下:
程序运行的结果如下:
示例
小明被拖欠工资,想走法律程序,找律师去申述这一个过程,使用代理模式律师就是代理者,小明就是被代理者,下面看看这样一个过程,代码应该怎样去实现。
从代码的角度来分,代理可以分为两种:一种是静态代理,另一种是动态代理。
静态代理就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。上面的例子实现就是静态代理。
动态代理类的源码是在程序运行期间根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。
下面我们实现动态代理,Java 提供了动态的代理接口 InvocationHandler,实现该接口需要重写 invoke() 方法:
使用这个动态代理修改小明案例中的客户类:
静态代理的缺点:
- 如果接口新增一个方法,除了所有实现类(真实主题类)需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
- 代理对象只服务于一种类型的对象,如果要服务多类型的对象。必须要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。
动态代理的优点:
- 可以通过一个代理类完成全部的代理功能,接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。当接口方法数量较多时,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
- 动态代理的应用使我们的类职责更加单一,复用性更强。
动态代理的缺点:
- 不能对类进行代理,只能对接口进行代理,如果我们的类没有实现任何接口,那么就不能使用这种方式进行动态代理。
根据适用范围,代理模式可以分为以下几种:
- 远程代理:为一个对象在不同的地址空间提供局部代表,这样系统可以将Server部分的事项隐藏。
- 虚拟代理:如果要创建一个资源消耗较大的对象,可以先用一个代理对象表示,在真正需要的时候才真正创建。
- 保护代理:用代理对象控制对一个对象的访问,给不同的用户提供不同的访问权限。
- 智能引用:在引用原始对象的时候附加额外操作,并对指向原始对象的引用增加引用计数。
ANDROID 源码中的实现
ANDROID 源码中多个地方都用到代理模式,比如 ActivityManagerProxy 这个代理类。其具体代理的是 ActivityManagerNative 的子类 ActivityManagerService。
ActivityManagerProxy 实现了 IActivityManager 接口,该接口定义了一些 Activity 相关的接口方法,其中有一些我们在应用开发中也时常接触到。IActivityManager 这个接口相当于代理模式中的抽象主题,那么真正的实现主题是 ActivityManagerNative 的子类 ActivityManagerService,这几个类大致的关系:
ActivityManagerProxy 实际上代理的是 ActivityManagerService,但是 ActivityManagerProxy 和 ActivityManagerService 是分别运行在不同的进程里(ActivityManagerProxy 是运行在应用的进程,而 ActivityManagerService 是运行在系统进程),所以它们之间的这个代理过程是跨进程的,这里跨进程是用到 Android 的 Binder 机制完成。不过 ActivityManagerProxy 在实际逻辑处理中并未过多地被外部类使用,因为在 Android 中管理与维护 Activity 相关信息的类是另外一个叫做 ActivityManager 的类,ActivityManager 虽然说管理着 Activity 信息,但是实质上大多数逻辑由 ActivityManagerProxy 承担,这里以其中的 getAppTasks 方法为例,在 ActivityManager 中 getAppTasks 方法逻辑如下:
getService() 其实返回的是一个 IActivityManager,那这个 IActivityManager 的实体类是什么呢?
ServiceManager.getService() 返回的是一个系统级的 Service,这个 Service 实际上是 ActivityManagerService,这里也完成创建一个对 ActivityManagerService 的 Client 代理对象 ActivityManagerProxy 实例。ActivityManagerProxy 中的 getAppTasks 方法逻辑就很明确,将数据打包跨进程传递给 Server 端 ActivityManagerService 处理并返回结果。
再来看看 ActivityManagerService 中的 getAppTasks:
Binder 跨进程通信机制
在 Android 中进程间通信我们通常使用到的是 binder 机制,binder 机制所使用到的四个基本模块是 Binder Client、Binder Server、ServerManager 和 Binder Driver。这四者之间的关系类似与网络访问,Binder Client 相当于我们的客户端 pc , Binder Server 相当于服务器,ServerManager 相当于 DNS 服务器,而 Binder Driver 则相当于一个路由器。其中 Binder Driver 实现在内核空间中,而其余的 3 者 Binder Client、Binder Server、ServerManager 实现在用户空间中。
Binder Client 与 Binder Server 之间的跨进程通信统一通过 Binder Driver 处理转发,对于 Binder Client 来说,其只需要知道自己要使用的 Binder 的名字以及该 Binder 实体在 ServerManager 中的 0 号引用即可,访问原理也比较简单,Binder Client 先是通过 0 号引用去访问 ServerManager 获取 Binder 的引用,得到引用后就可以像普通方法那样调用 Binder 实体方法。最后我们的 ServerManager 则用来管理 Binder Server,Binder Client 可以通过它来查询 Binder Server接口,刚才提到过 Binder Client 可以通过 ServerManager 来获取 Binder 的引用,这个 Binder 引用就是由 ServerManager 来转换的。
可以想象成 Binder Driver 就是一个管道,ServerManager 是一个注册表,所有的 Binder Client 和 Binder Server 都要在它那里注册,Binder Client 也通过 ServerManager 去查找对应的 Binder Server。最后,Binder Client 和 Binder Server 其实实现的接口是一样的,所以大家可以联想到 Binder 机制其实也是一种代理模式。
实战
Notification 适配。通过代理模式解决各个版本的 Notification 碎片化问题,为每种不同的 Notification 样式定义一个类,这里以正常的 64dp Height、256dp Height 和 headsUpContentView 为例,先定义一个抽象类表示通知。
Notify 声明两个成员变量处理与通知相关的逻辑,且让所有子类共有。两个抽象方法 send 和 cancel 均有具体的子类去实现。
最后定义一个代理类来整合上面的几个类。
在 NotifyProxy 类中定义一个 Notify 类型的成员变量,在构造方法里根据 SDK 版本的不同去实例化不同的 Notify 子类,最终由该类的 send 和 cancel 方法去调用不同的逻辑实现,这样一来,我们的客户端也就是我们的 Activity 类中就很简单了,直接调用 NotifyProxy 中的方法即可。